﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Duellum.Core;
using Duellum.Core.Resources;
using JpLabs.Geometry;
using JpLabs.RTree;
using JpLabs.Extensions;
using System.Windows;

namespace Duellum.Map
{
	public interface IDuelMapEntry
	{
		DuelAbstraction Object	{ get; }
	    PointInt Position		{ get; }
	}
	
	internal class DuelMapEntry : IRTreeEntry, IDuelMapEntry
	{
		ITreeNode ITreeNode.Parent	{ get; set; }
	    IShape ITreeNode.Shape		{ get { return Position; } }
		object IRTreeEntry.Data			{ get { return Object; } }

		public DuelAbstraction Object	{ get; set; }
	    public PointInt Position		{ get; set; }
		
		public DuelMapEntry(DuelAbstraction obj, PointInt pos)
		{
			Object = obj;
			Position = pos;
		}
	}
	
	//public interface IDuelMapLookupSection : IEnumerable<IDuelMapEntry>
	//{
	//    IEnumerable<IDuelMapEntry> Get(PointInt pos);
	//    T[,,] ToGrid<T>(Func<IEnumerable<IDuelMapEntry>,T> selector, T valueOnEmpty);
	//}

	public static class DuelMapAttributes
	{
		public const string FormatVersion = "FormatVersion";
		public const string Name = "Name";
	}

	public class DuelMap
	{	
		public const string MapFileExtension = ".MAPX";
		
		public IDictionary<XName,string> Attributes { get; private set; }
		
		//length, width, and height
		public int DimX { get { return Box.Upper.X - Box.Lower.X; } }
		public int DimY { get { return Box.Upper.Y - Box.Lower.Y; } }
		public int DimZ { get { return Box.Upper.Z - Box.Lower.Z; } }
		//public int[] CeilingHeight { get; protected set; }
		
		public int GroundLevel { get; protected set; }
		
		protected ITree Tree		{ get; private set; }
		public BoundingBoxInt Box	{ get; set; }
		
		public DuelMap()
		{
			const int DIMENSIONS = 3;
			const int MAX_CHILDREN_PER_NODE = 5; //how about 7? Actually, the optimal value depends on the map itself
			this.Tree = new RStarTree(DIMENSIONS, MAX_CHILDREN_PER_NODE);
			
			this.GroundLevel = 0;
			this.Attributes = new Dictionary<XName,string>();
		}

		internal DuelMap(int groundLevel) : this()
		{
			this.GroundLevel = groundLevel;
		}

		public void AddEntry(DuelAbstraction obj, int x, int y, int z)
		{
			Tree.Add(new DuelMapEntry(obj, new PointInt(x, y, z)));
		}
		
		public void AddEntry(DuelAbstraction obj, PointInt pos)
		{
			if (pos.Dimensions != 3) throw new ArgumentException();
			Tree.Add(new DuelMapEntry(obj, pos));
		}
		
		public bool RemoveEntry(IDuelMapEntry entry)
		{
			if (!(entry is IRTreeEntry)) return false;
			return Tree.Remove((IRTreeEntry)entry, null);
		}

		public IEnumerable<IDuelMapEntry> QueryAll()
		{
			return Tree.Query(new GetAllQuery()).Cast<IDuelMapEntry>();
		}
		
		public IEnumerable<IDuelMapEntry> Query(IShape shape)
		{
			return Tree.Query(new IntersectsShapeQuery(shape)).Cast<IDuelMapEntry>(); //.OfType<T>()
		}

		public IEnumerable<IDuelMapEntry> Query(int x, int y, int z)
		{
			return this.Query(new PointInt(x, y, z));
		}

		//private static StreamReader OpenFile(string path)
		//{
		//    //return new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read));
		//    return new StreamReader(path);
		//}

		static public DuelMap LoadMap(DuelDomain domain, StreamReader reader)
		{
			if (domain == null) throw new ArgumentNullException("domain");
			
			//TODO: find a better way to discover what is the format of the map (i.e.: .map or .mapx)
			
			try
			{
				var fileStream = reader.BaseStream as FileStream;
				if (fileStream != null) {
					var extension = Path.GetExtension(fileStream.Name).ToUpperInvariant();
					
					if (extension == DuelMap.MapFileExtension) return DuelMapDecoder.LoadMap(domain, reader);
					if (extension == MapLegacyReader.MapFileExtension) return MapLegacyReader.LoadMap(domain, reader);
				}
				
				throw new NotSupportedException("Currently, only FileStreams are supported. Sorry for the incovenience.");
			}
			finally
			{
				reader.Dispose();
			}
		}

		static public DuelMap LoadMap(DuelDomain domain, Uri uri)
		{
			return LoadMap(domain, new StreamReader(DuelResources.GetStreamFromUri(uri)));
		}

		//static public DuelMap LoadMap(DuelDomain domain, string path)
		//{
		//    return LoadMap(domain, OpenFile(path));
		//}
		
		const double HEX_L = 0.57735;			//12.6667
		const double SCALE_X = HEX_L * 1.5;		//19
		const double SCALE_Y = 1;				//22
		const double HEX_WIDTH = HEX_L * 2;		//26
		const double HEX_HEIGHT = SCALE_Y;		//23
		
		
		
		static public Point PositionToOriginPoint(PointInt p)
		{
		    //if (p.Dimensions != 2) throw new ArgumentException();
		    return PositionToOriginPoint(p.X, p.Y);
		}

        static public Point PositionToOriginPoint(int x, int y)
        {
			const double HALF_Y = .5;
			return new Point(
				x * SCALE_X,
				(y + ((x % 2) * HALF_Y)) * SCALE_Y
			);
        }
        
		static public Point PositionToCenterPoint(PointInt pos)
		{
		    return PositionToCenterPoint(pos.X, pos.Y);
		}

        static public Point PositionToCenterPoint(int x, int y)
        {
			return PositionToOriginPoint(x, y).Translate(HEX_WIDTH / 2, HEX_HEIGHT / 2);
        }

		static public double AngleBetwen(PointInt from, PointInt to)
		{
			var fromPoint = DuelMap.PositionToOriginPoint(from);
			var toPoint = DuelMap.PositionToOriginPoint(to);
			
			return AngleBetwen(fromPoint, toPoint);
		}
		
		static public double AngleBetwen(Point from, Point to)
		{
			double diffX = to.X - from.X;
			double diffY = to.Y - from.Y;
			
			return Math.Atan2(diffY, diffX);
		}
	}
}
